#ifndef EVENT_H
#define EVENT_H

#define BIT(x) (1 << x)
#include <iostream>
#include <functional>
#include <string>

namespace CC
{

	// Events in Hazel are currently blocking, meaning when an event occurs it
	// immediately gets dispatched and must be dealt with right then an there.
	// For the future, a better strategy might be to buffer events in an event
	// bus and process them during the "event" part of the update stage.

	enum EventType
	{
		EventType_None = 0,
		WindowClose,
		WindowResize,
        WindowFramebufferResize,
		WindowFocus,
		WindowLostFocus,
		WindowMoved,
		AppTick,
		AppUpdate,
		AppRender,
		KeyPressed,
		KeyReleased,
		KeyTyped,
		MouseButtonPressed,
		MouseButtonReleased,
		MouseButtonCancel,
		MouseMoved,
		MouseScrolled,
        KeyWinPressed
	};

	enum EventCategory
	{
		EventCategory_None = 0,
		EventCategoryApplication = BIT(0),
		EventCategoryInput = BIT(1),
		EventCategoryKeyboard = BIT(2),
		EventCategoryMouse = BIT(3),
		EventCategoryMouseButton = BIT(4),
		EventCategoryKeyboardWin = BIT(5),
	};

#define EVENT_CLASS_TYPE(typename)                      \
	static EventType GetStaticType()                \
	{                                               \
		return EventType::typename;                     \
	}                                               \
	virtual EventType GetEventType() const override \
	{                                               \
		return GetStaticType();                     \
	}                                               \
	virtual const char *GetName() const override    \
	{                                               \
		return #typename;                               \
	}

#define EVENT_CLASS_CATEGORY(categoryname)            \
	virtual int GetCategoryFlags() const override \
	{                                             \
		return categoryname;                          \
	}

	class Event
	{
	public:
		virtual ~Event() = default;

		bool Handled = false;

		virtual EventType GetEventType() const = 0;
		virtual const char *GetName() const = 0;
		virtual int GetCategoryFlags() const = 0;
		virtual std::string ToString() const { return GetName(); }

		bool IsInCategory(EventCategory category)
		{
			return GetCategoryFlags() & category;
		}
	};

	typedef std::function<void(Event &)> EventCallbackFn;

	class EventDispatcher
	{
	public:
		EventDispatcher(Event &event)
			: m_Event(event)
		{
		}

		// F will be deduced by the compiler
		template <typename T>
		void Dispatch(const std::function<void(T &)> &func)
		{
			if (m_Event.GetEventType() == T::GetStaticType())
			{
				func(static_cast<T &>(m_Event));
			}
		}

	private:
		Event &m_Event;
	};

	inline std::ostream &operator<<(std::ostream &os, const Event &e)
	{
		return os << e.ToString().c_str();
	}

}

#endif